package system

import (
	"gin-admin/common/dto/request"
	"gin-admin/common/dto/response"
	"gin-admin/global"
	"time"

	"gin-admin/core"
	"gin-admin/model"
	"gin-admin/repo"
	serviceRequest "gin-admin/service/dto/request"
	serviceResponse "gin-admin/service/dto/response"
	"gin-admin/utils"
)

type UserService struct {
	CrudService    *repo.Repository[model.User]
	CaptchaService CaptchaService
}

func NewUserServiceApp(crudService *repo.Repository[model.User]) *UserService {
	return &UserService{
		CrudService:    crudService,
		CaptchaService: *NewCaptchaServiceApp(),
	}
}

func userNotFoundHandler() *response.ServiceError {
	return response.WrapError(response.UserNotFoundCode, response.GetStatusCode(response.UserNotFoundCode).Message)
}

func userExistsHandler() *response.ServiceError {
	return response.WrapError(response.UserAccountExistsCode, response.GetStatusCode(response.UserAccountExistsCode).Message)
}

func (us *UserService) CreateUserService(requestData *serviceRequest.CreateUserRequest) *response.ServiceError {
	condition := core.BuildQueryCondition(map[string]any{
		"account": requestData.Account,
	})
	password, _ := utils.GeneratePassword(requestData.Password)
	user := model.User{
		Account:  requestData.Account,
		Password: password,
	}
	err := us.CrudService.GenericCreateWithData(condition, &user, "OR", userExistsHandler)
	return err
}

func (us *UserService) DeleteUserByIdService(requestData *request.IDRequest) (bool, *response.ServiceError) {
	user, err := us.CrudService.GenericQueryDetailById(requestData.ID, userNotFoundHandler)
	if err != nil {
		return false, err
	}
	if user.Account == "admin" {
		return false, response.WrapError(
			response.SystemPermissionCode,
			response.GetStatusCode(response.SystemPermissionCode).Message,
		)
	}
	return us.CrudService.GenericLogicDeleteById(requestData.ID, "", userNotFoundHandler)
}

func (us *UserService) ModifyUserService(requestData *serviceRequest.ModifyUserRequest) *response.ServiceError {
	_, err := us.CrudService.GenericQueryDetailById(requestData.ID, userNotFoundHandler)
	if err != nil {
		return err
	}

	success, err := us.CrudService.GenericModifyByCondition(requestData.ID, &requestData, userNotFoundHandler)
	if err != nil {
		return err
	}
	if !success {
		return response.WrapError(response.UserModifyInfoFailedCode, response.GetStatusCode(response.UserModifyInfoFailedCode).Message)
	}
	return nil
}

func (us *UserService) GetUserInfoService(id string) (*serviceResponse.UserResponse, *response.ServiceError) {
	var userResponse serviceResponse.UserResponse
	user, err := us.CrudService.GenericQueryDetailById(id, userNotFoundHandler)
	if err != nil {
		return nil, err
	}
	if err := utils.ModelToStruct(&userResponse, &user); err != nil {
		return nil, err
	}
	return &userResponse, nil
}

func (us *UserService) GetUserPageService(requestData *serviceRequest.UserQueryRequest) (*response.PageResponse[serviceResponse.UserResponse], *response.ServiceError) {
	var paginationResponse response.PageResponse[serviceResponse.UserResponse]
	condition := map[string]interface{}{
		"account": requestData.Account,
	}

	pageData, users, err := us.CrudService.GenericQueryPageByCondition(requestData.Page, requestData.Size, requestData.OrderBy, requestData.OrderType, condition)
	if err != nil {
		return nil, err
	}

	var userResponse serviceResponse.UserResponse
	for _, user := range users {
		err := utils.ModelToStruct(&userResponse, &user)
		if err != nil {
			return nil, err
		}
		paginationResponse.List = append(paginationResponse.List, userResponse)
	}
	paginationResponse.Page = pageData.Page
	paginationResponse.Size = pageData.Size
	paginationResponse.Total = pageData.Total
	return &paginationResponse, nil
}

func (us *UserService) GetUserListService(requestData *serviceRequest.UserQueryRequest) (*[]serviceResponse.UserResponse, *response.ServiceError) {
	condition, err := utils.StructToMapWithoutZero(&requestData)
	if err != nil {
		return nil, err
	}
	users, err := us.CrudService.GenericQueryListByCondition("", "", condition)
	if err != nil {
		return nil, err
	}
	var userListResponse []serviceResponse.UserResponse
	var userResponse serviceResponse.UserResponse
	for _, user := range users {
		if err := utils.ModelToStruct(&userResponse, &user); err != nil {
			return nil, err
		}
		userListResponse = append(userListResponse, userResponse)
	}
	return &userListResponse, nil
}

func (us *UserService) UserLoginService(requestData *serviceRequest.LoginRequest) (*serviceResponse.LoginResponse, *response.ServiceError) {
	var loginResponse serviceResponse.LoginResponse
	verify, err := us.CaptchaService.VerifyCaptchaService(&requestData.VerifyCaptchaRequest)
	if err != nil {
		return nil, err
	}
	if !verify {
		return nil, response.WrapError(response.CaptchaInvalidCode, response.GetStatusCode(response.CaptchaInvalidCode).Message)
	}
	condition := map[string]interface{}{
		"account": requestData.Account,
	}
	user, err := us.CrudService.GenericQueryDetailByContition(condition, userNotFoundHandler)
	if err != nil {
		return nil, err
	}
	if !utils.VerifyPasswordWithHashedPassword(user.Password, requestData.Password) {
		return nil, response.WrapError(
			response.UserAccountOrPasswordFailedCode,
			response.GetStatusCode(response.UserAccountOrPasswordFailedCode).Message,
		)
	}
	expiresAt, token, tokenErr := utils.GenerateToken(user.UserID, user.IsAdmin)
	if tokenErr != nil {
		return nil, response.WrapError(
			response.UserLoginFailedCode,
			response.GetStatusCode(response.UserLoginFailedCode).Message,
		)
	}
	refresToken, refresTokenErr := utils.RefreshToken(token)
	if refresTokenErr != nil {
		return nil, response.WrapError(
			response.UserLoginFailedCode,
			response.GetStatusCode(response.UserLoginFailedCode).Message,
		)
	}
	redisUtils := &utils.RedisUtils{}
	if err := redisUtils.Set(ctx, user.UserID, token, global.GA_CONFIG.Redis.ExpiresAt*time.Second); err != nil {
		return nil, response.WrapError(
			response.UserLoginFailedCode,
			response.GetStatusCode(response.UserLoginFailedCode).Message,
		)
	}
	loginResponse = serviceResponse.LoginResponse{
		Token:        token,
		RefreshToken: refresToken,
		ExpiresAt:    expiresAt,
	}
	return &loginResponse, nil
}
